package de.sendorian.db;

import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Repository;


/**
 * Super class for DAOs which declares the essential functions.
 * 
 * @author sendorian
 * 
 */
@Repository
public abstract class RepositoryBase<T> {
    protected Logger logger;

    private EntityManager entityManager;

    protected final Class<T> entity;

    protected String entityName;


    protected RepositoryBase(Class<T> entity) {
        this.entity = entity;
        entityName = entity.getSimpleName();
        logger = Logger.getLogger(this.getClass().getName());
    }


    protected EntityManager getEntityManager() {
        return entityManager;
    }


    @PersistenceContext
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }


    protected void begin() {
        getEntityManager().getTransaction().begin();
    }


    protected void commit() {
        getEntityManager().getTransaction().commit();
    }


    protected void rollback() {
        try {
            getEntityManager().getTransaction().rollback();
        } finally {
            close();
        }
    }


    public void close() {
        getEntityManager().close();
    }


    public <E> E update(E entity) {
        begin();
        E result = getEntityManager().merge(entity);
        commit();
        return result;
    }


    public <E> E save(E entity) {
        begin();
        getEntityManager().persist(entity);
        commit();
        return entity;
    }


    public void saveOrUpdate(Object entity) {
        Integer id = null;
        try {
            id = (Integer) entity.getClass().getMethod("getId", null).invoke(entity, null);
        } catch (Exception e) {
            throw new IllegalArgumentException("Entity has no ID attribute! " + entity, e);
        }
        if (id != null && id.intValue() > 0) {
            update(entity);
        } else {
            save(entity);
        }
    }


    public void delete(Object entity) {
        begin();
        getEntityManager().remove(entity);
        commit();
    }


    public int deleteById(Object id) {
        return deleteByAttribute("id", id);
    }


    public int deleteByAttribute(String attribute, Object value) {
        begin();
        int count = getEntityManager().createQuery("delete from " + entityName + " where " + attribute + " = :value")
                .setParameter("value", value).executeUpdate();
        commit();
        return count;
    }


    /**
     * Loads the entity with the given ID from the database. The Type of the
     * entity is set through the type parameter (and can be of any entity
     * type!).
     * 
     * @return The entity, null if not found.
     */
    public <E> E findById(Class<E> type, Object key) {
        return getEntityManager().find(type, key);
    }


    /**
     * Loads the entity with the given ID from the database. The Type of the
     * entity is set through the type of this DAO.
     * 
     * @return The entity, null if not found.
     */
    public T findById(Object id) {
        return getEntityManager().find(entity, id);
    }


    public boolean existsById(Object key) {
        return findById(key) != null;
    }


    @SuppressWarnings("unchecked")
    public List<T> findByAttribute(String attribute, Object value) {
        try {
            return createQuery(attribute, value).getResultList();
        } catch (EmptyResultDataAccessException e) {
            logger.debug("No " + entityName + " found with " + attribute + " \"" + value + "\".");
        }
        return Collections.emptyList();
    }


    @SuppressWarnings("unchecked")
    public T findByUniqueAttribute(String attribute, Object value) {
        try {
            return (T) createQuery(attribute, value).getSingleResult();
        } catch (EmptyResultDataAccessException e) {
            logger.debug("No " + entityName + " found with " + attribute + " \"" + value + "\".");
        }
        return null;
    }


    /**
     * Creates a simple query based on the entity type of this DAO and one
     * parameter that has to have a given value.
     * 
     * @param paramName
     *            name of the property
     * @param paramValue
     *            the value
     * @return a query representing the given parameters
     */
    protected Query createQuery(String attribute, Object value) {
        String jpql = "select e from " + entityName + " e where e." + attribute + " = :value";
        Query query = getEntityManager().createQuery(jpql);
        query.setParameter("value", value);
        return query;
    }


    public void deleteAll() {
        begin();
        getEntityManager().createQuery("delete from " + entityName).executeUpdate();
        commit();
    }


    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        String jpql = "select e from " + entityName + " e";
        return getEntityManager().createQuery(jpql).getResultList();
    }


    /**
     * Searches for entities for which the given String property matches the
     * given valuePattern (like, ignore case).
     * 
     */
    @SuppressWarnings("unchecked")
    public List<T> search(String attribute, String valuePattern) {
        if (attribute == null || valuePattern == null)
            throw new IllegalArgumentException("null values not allowed.");

        try {
            String jpql = "select e from " + entityName + " e where upper(e." + attribute + ") like :value";
            Query query = getEntityManager().createQuery(jpql);
            query.setParameter("value", valuePattern.toUpperCase());
            return query.getResultList();
        } catch (EmptyResultDataAccessException e) {
            logger.debug("No " + entityName + " found with " + attribute + " \"" + valuePattern + "\".");
        }
        return null;
    }

}
